<?php

/*
 * phptoolsuite工具套件公共头文件
 * @version 2018-1-12 SoChishun Added.
 * @version 2018-1-31 sochishun 增加redirect跳转函数
 */

// TOKEN_EXP 超时时间 (1 hours, 1 minutes, 30 seconds)
JWTLite::$TOKEN_EXP = '1 hours';
// TOKEN_AUD 允许运行的服务器列表(适用于分布式环境,如果是负载均衡+高防IP环境,则填入高防IP即可),多个之间以逗号隔开,中间不要有空格
JWTLite::$TOKEN_AUD = '127.0.0.1,45.126.121.80';
// TOKEN签名密钥
JWTLite::$TOKEN_KEY = 'U6rxIVp8!';
// TOKEN自动延期时长, 每次页面跳转操作会重新生成一个延期过的TOKEN,类似session的效果,只要一直操作就一直不会过期
JWTLite::$TOKEN_REFRESH_SECOND = 1800;

/**
 * 页面访问验证配置
 */
JWTLite::$AUTH_CONFIG = [
    'admins' => ['admin' => 'passWd123', 'testuser' => 'passWd123'], // 允许登录的帐号列表['登录名'=>'密码']
    'ips' => ['127.0.0.1', '110.52.27.168'], // 允许访问的ip地址列表
    'current_authtype' => 1, // 指定页面访问验证类型：0:无需验证;1:帐号密码验证;2:IP验证;3:混合验证(先IP验证,IP验证失败则转入帐号密码验证)
];

// =======================公共类和函数==========================
/**
 * 用户登录认证类
 * @version 2017-12-15
 */
class JWTLite {

    /**
     * TOKEN_EXP 超时时间 (1 hours, 1 minutes, 30 seconds)
     * @var type 
     */
    public static $TOKEN_EXP = '1 hours';

    /**
     * TOKEN_AUD 允许运行的服务器列表(适用于分布式环境,如果是负载均衡+高防IP环境,则填入高防IP即可),多个之间以逗号隔开,中间不要有空格
     * @var type 
     */
    public static $TOKEN_AUD = '127.0.0.1,41.92.44.100';

    /**
     * TOKEN签名密钥
     * @var type 
     */
    public static $TOKEN_KEY = 'S8lxUmpx!';

    /**
     * TOKEN自动延期时长, 每次页面跳转操作会重新生成一个延期过的TOKEN,类似session的效果,只要一直操作就一直不会过期
     * @var type 
     */
    public static $TOKEN_REFRESH_SECOND = 1800;

    /**
     * 页面访问验证配置
     * @var type 
     */
    public static $AUTH_CONFIG = array();

    /**
     * 登录结果
     * @var type compact('loginid', 'token', 'loginexp')
     */
    public static $LOGIN_RESULT = array();

    /**
     * 生成签名
     * @param array $data
     * @return array array(exp,sign,...)
     * @version 2017-11-3
     */
    public static function gen_sign(array $data = array()) {
        $tokenInfo = array(
            "exp" => strtotime("30 minutes"), // 默认TOKEN有效期30分钟
            "aud" => self::$TOKEN_AUD,
        );
        $data = array_merge($tokenInfo, $data);
        ksort($data);
        $md5Str = md5(self::$TOKEN_KEY . json_encode($data));
        $data['sign'] = substr($md5Str, 20, 6);
        unset($data['aud']);
        return $data;
    }

    /**
     * 验证签名
     * @param array $data array(exp,sign,...)
     * @return array
     * @version 2017-11-3
     */
    public static function check_sign($data) {
        if (empty($data['exp']) || empty($data['sign'])) {
            return array('status' => false, 'info' => '签名内容不规范');
        }
        $exp = $data['exp'];
        if ($exp < time()) {
            return array('status' => false, 'info' => '签名已过期');
        }
        if (self::$TOKEN_AUD) {
            $audArr = explode(',', self::$TOKEN_AUD);
            $hostIp = self::get_host_ip();
            if (!in_array($hostIp, $audArr)) {
                return array('status' => false, 'info' => '服务器不在白名单<span style="display:none">' . $hostIp . '</span>');
            }
        }
        $postSign = $data['sign'];
        unset($data['sign']);
        $data['aud'] = self::$TOKEN_AUD;
        ksort($data); // aud,exp,user
        $localSign = substr(md5(self::$TOKEN_KEY . json_encode($data)), 20, 6);
        if ($localSign != $postSign) {
            return array('status' => false, 'info' => '签名非法');
        }
        return array('status' => true, 'info' => '');
    }

    /**
     * 获取表单令牌
     * @return string
     * @version 2017-11-3 由session改为token
     */
    public static function get_token($data) {
        return base64_encode(json_encode(self::gen_sign($data)));
    }

    /**
     * 解析token
     * @param string $post_token
     * @return array
     * @version 2017-11-3 added.
     */
    public static function parse_token($post_token = '') {
        if (!$post_token) {
            $post_token = htmlspecialchars(trim(input('token')));
        }
        if (!$post_token) {
            return ['status' => false, 'info' => 'TOKEN为空'];
        }
        $json = base64_decode($post_token);
        if (false === $json) {
            return ['status' => false, 'info' => 'TOKEN内容无效'];
        }
        $post = json_decode($json, true);
        if (!$post) {
            return ['status' => false, 'info' => 'TOKEN格式错误'];
        }
        $sigResult = self::check_sign($post);
        if (!$sigResult['status']) {
            return $sigResult;
        }
        return ['status' => true, 'info' => $post];
    }

    /**
     * 生成新的延期过的token
     * @param integer $exp 延期时间
     * @param string $token 旧的token字符串
     * @return string
     * @version 2017-11-29
     */
    public static function refresh_token($exp, $token) {
        $tokenInfo = self::parse_token($token);
        $data = $tokenInfo['info'];
        unset($data['sign']);
        $data['exp'] = strtotime(self::$TOKEN_EXP) + self::$TOKEN_REFRESH_SECOND;
        return self::get_token($data);
    }

    /**
     * 用户登录操作
     * @param type $admins
     * @param type $urlparams 额外的浏览器参数
     */
    public static function dologin($admins, $urlparams = array()) {
        $posttoken = htmlspecialchars(input('login_token'));
        if ($posttoken) {
            $tokenInfo = self::parse_token($posttoken);
            $goback_btn = '<a href="?login">[返回]</a>';
            if (!$tokenInfo['status']) {
                exit($tokenInfo['info'] . $goback_btn);
            }
            $loginid = strval(htmlspecialchars(input('user')));
            $loginpwd = htmlspecialchars(input('pass'));
            if (!$loginid || !$loginpwd) {
                exit('表单填写不完整!' . $goback_btn);
            }
            if (!array_key_exists($loginid, $admins)) {
                exit('帐号不存在!' . $goback_btn);
            }
            if ($loginpwd != $admins[$loginid]) {
                exit('密码错误!' . $goback_btn);
            }
            $url = '?token=' . self::get_token(array('exp' => strtotime(self::$TOKEN_EXP), 'user' => $loginid));
            if ($urlparams) {
                $url .= '&' . http_build_query($urlparams);
            }
            header('location:' . $url);
        } else {
            echo '<form method="post" action="?action=login">';
            echo '<input type="text" name="user" placeholder="用户名" required="required" />';
            echo '<input type="password" name="pass" placeholder="密码" required="required" />';
            echo '<input type="hidden" name="login_token" value="', self::get_token(array('exp' => strtotime('3 minutes'), 'action' => 'login')), '" />';
            if ($urlparams) {
                foreach ($urlparams as $key => $val) {
                    echo '<input type="hidden" name="', $key, '" value="', $val, '" />';
                }
            }
            echo '<button type="submit">登录</button><button type="reset">重置</button> ';
            echo $_SERVER['REMOTE_ADDR'];
            echo '</form>';
        }
    }

    /**
     * 访问权限验证
     * @param array $urlparams
     */
    public static function check_login($urlparams = array()) {
        $curAuthtypeId = self::$AUTH_CONFIG['current_authtype'];
        $curAuthStatus = true; // 权限验证状态
        if ($curAuthtypeId == 2 || $curAuthtypeId == 3) { // IP验证
            $http_host = $_SERVER['REMOTE_ADDR'];
            if (!in_array($http_host, self::$AUTH_CONFIG['ips'])) {
                $curAuthStatus = false; // IP验证失败
                if ($curAuthtypeId == 2) {
                    exit('IP [' . $http_host . '] 禁止访问!');
                }
            }
        }

        $loginid = false; // 用户登录ID
        $loginexp = ''; // token过期时间提示
        $token = htmlspecialchars(trim(input('token'))); // 用户登录会话Token
        if ($curAuthtypeId == 1 || ($curAuthtypeId == 3 && !$curAuthStatus)) { // 帐号密码验证
            if ($token) { // 解析登录会话token信息
                $tokenInfo = self::parse_token($token);
                if ($tokenInfo['status']) {
                    $loginid = $tokenInfo['info']['user'];
                    $loginexp = date('Y-m-d H:i:s', $tokenInfo['info']['exp']);
                    // 自动续期功能 2017-11-29
                    if (self::$TOKEN_REFRESH_SECOND > 0) {
                        $refreshToken = self::refresh_token(self::$TOKEN_REFRESH_SECOND, $token);
                        $token = $refreshToken;
                    }
                } else {
                    echo '<div style="font-size:12px; color:#F00">', $tokenInfo['info'], '</div>';
                }
            }
            // 注销登录
            if (input('action') == 'logout') {
                header('location:?logout-success');
                exit;
            }
            if (!$loginid) {
                self::dologin(self::$AUTH_CONFIG['admins'], $urlparams);
                exit;
            }
        }
        self::$LOGIN_RESULT = compact('loginid', 'token', 'loginexp');
    }

    /**
     * 返回服务器IP
     * 参考：SERVER_NAME 和HTTP_HOST的区别 (http://blog.sina.com.cn/s/blog_6d96d3160100q39x.html)
     * @since 1.0 2017-10-13 SoChishun Added.
     */
    public static function get_host_ip() {
        //return $_SERVER['HTTP_HOST']; // 有服务器域名则优先返回域名,否则返回服务器ip
        return gethostbyname($_SERVER['SERVER_NAME']); // 返回服务器IP
    }

}

/**
 * FileUtil 文件通用类
 * @version 2018-1-13 SoChishun Added.
 */
class FileUtil {

    /**
     * PHP高效遍历文件夹（大量文件不会卡死）
     * <br />外层要套上UL标签
     * @param string $token JWT登录会话TOKEN信息
     * @param string $path 目录路径
     * @param integer $level 目录深度
     * @param boolean $showfile 是否显示文件(否则只遍历显示目录)
     * @param string $curpath 当前输入的目录
     * @param string $dirpath
     */
    static function scandirEcho($token, $path = './', $level = 0, $showfile = true, $curpath = '', $dirpath = '') {
        if (!file_exists($path)) {
            echo '路径[', $path, ']不存在';
            return;
        }
        $file = new FilesystemIterator($path);
        $filename = '';
        $url = '';
        $prefix = ''; // 树形层级图形
        $isactive = ''; // 选中效果类
        foreach ($file as $fileinfo) {
            $filename = $fileinfo->getFilename();
            $filepath = $path . $filename;
            $isactive = ($curpath == $filepath ? ' class="active"' : '');
            $prefix = $level > 0 ? ('|' . str_repeat('--', $level)) : '';
            if ($fileinfo->isDir()) {
                $filepath = $filepath . '/';
                $url = '<a title="' . $filepath . '" href="?path=' . $filepath . '&dirpath=' . $filepath . '&token=' . $token . '">' . short_text($filename, 12) . '</a>';
                $url .= '<span style="font-weight:normal">[<a title="新窗口打开目录 ' . $filepath . '" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '" target="_blank">新</a>]';
                $url .= '[<a title="刷新目录" href="?path=' . ($dirpath ? $dirpath : ROOTPATH) . '&dirpath=' . $dirpath . '&token=' . $token . '">刷</a>]';
                $url .= '[<a title="清空目录" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '&action=clear" onclick="return confirm(\'您确定要清空当前目录吗?\')">清</a>]';
                $url .= '[<a title="删除目录" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '&action=delete" onclick="return confirm(\'您确定要删除当前目录吗?\')">删</a>]</span>';
                echo '<li', $isactive, '><strong>' . $prefix . $url . '/</strong> <a href="#" class="btn_switch" title="收起">[-]</a>' . PHP_EOL;
                echo '<ul>';
                self::scandirEcho($filepath, $level + 1, $showfile, $curpath, $dirpath, $token);
                echo '</ul>';
                echo '</li>';
            } else {
                if ($showfile) {
                    $url = '<a title="' . $filepath . '" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '">' . short_text($filename, 12) . '</a> [<a title="新窗口打开文件 ' . $filepath . '" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '" target="_blank">新</a>][<a title="删除文件" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '&action=delete" onclick="return confirm(\'您确定要删除吗?\')">删</a>][<a title="[右键点击下载]" href="' . $filepath . '" target="_blank">下</a>]';
                    echo '<li', $isactive, '>' . $prefix . $url . '</li>' . PHP_EOL;
                }
            }
        }
    }

    /**
     * 显示由scandirReturn方法返回的文件列表
     * @param string $token JWT登录会话TOKEN信息
     * @param string $path 目录路径
     * @param integer $level 目录深度
     * @param string $curpath 当前输入的目录
     * @param string $dirpath 父路径
     * @param array $option 选项, 如果是null则隐藏所有按钮
     * @version 2018-1-11
     * @version 2018-1-13 由函数重构为类方法; 新增$option选项参数,$showfile参数合并到$option选项参数中
     */
    static function showFiles($token, $files, $level = 0, $curpath = '', $dirpath = '', $option = array()) {
        if (!$files) {
            return;
        }
        if (is_null($option)) {
            $defaultopt = false;
        } else {
            $defaultopt = array(
                'show_file' => true, // 是否显示文件(否则只遍历显示目录)
                'btn_refresh_visible' => true, // 是否显示目录刷新按钮
                'btn_open_visible' => true, // 是否显示目录新窗口打开按钮
                'btn_clear_visible' => true, // 是否显示目录清空按钮
                'btn_rmdir_visible' => true, // 是否显示目录删除按钮
                'btn_unlink_visible' => true, // 是否显示文件删除按钮
                'btn_download_visible' => true, // 是否显示文件下载按钮
            );
            if ($option) {
                $defaultopt = array_merge($defaultopt, $option);
            }
        }
        $url = '';
        $prefix = ''; // 树形层级图形
        $isactive = ''; // 选中效果类
        foreach ($files as $filename => $fileinfo) {
            $filepath = $fileinfo['path'];
            $isactive = ($curpath == $filepath ? ' class="active"' : '');
            $prefix = $level > 0 ? ('|' . str_repeat('--', $level)) : '';
            if ($fileinfo['type'] == 'dir') {
                $filepath = $filepath . '/';
                $url = '<a title="' . $filepath . '" href="?path=' . $filepath . '&dirpath=' . $filepath . '&token=' . $token . '">' . short_text($filename, 12) . '</a>';
                $url .= '<span style="font-weight:normal">';
                if ($defaultopt && $defaultopt['btn_open_visible']) {
                    $url .= '[<a title="新窗口打开目录 ' . $filepath . '" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '" target="_blank">新</a>]';
                }
                if ($defaultopt && $defaultopt['btn_refresh_visible']) {
                    $url .= '[<a title="刷新目录" href="?path=' . ($dirpath ? $dirpath : ROOTPATH) . '&dirpath=' . $dirpath . '&token=' . $token . '">刷</a>]';
                }
                if ($defaultopt && $defaultopt['btn_clear_visible']) {
                    $url .= '[<a title="清空目录" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '&action=clear" onclick="return confirm(\'您确定要清空当前目录吗?\')">清</a>]';
                }
                if ($defaultopt && $defaultopt['btn_rmdir_visible']) {
                    $url .= '[<a title="删除目录" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '&action=delete" onclick="return confirm(\'您确定要删除当前目录吗?\')">删</a>]</span>';
                }
                echo '<li', $isactive, '><strong>' . $prefix . $url . ($defaultopt ? '/' : '') . '</strong>' . PHP_EOL;
                if (!empty($fileinfo['children'])) {
                    echo '<a href="#" class="btn_switch" title="收起">[-]</a>';
                    echo '<ul>';
                    self::showFiles($token, $fileinfo['children'], $level + 1, $curpath, $dirpath, $option);
                    echo '</ul>';
                }
                echo '</li>';
            } else {
                if ($defaultopt && $defaultopt['show_file']) {
                    $url = '<a title="' . $filepath . '" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '">' . short_text($filename, 12) . '</a>';
                    if ($defaultopt && $defaultopt['btn_open_visible']) {
                        $url .= '[<a title="新窗口打开文件 ' . $filepath . '" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '" target="_blank">新</a>]';
                    }
                    if ($defaultopt && $defaultopt['btn_unlink_visible']) {
                        $url .= '[<a title="删除文件" href="?path=' . $filepath . '&dirpath=' . $dirpath . '&token=' . $token . '&action=delete" onclick="return confirm(\'您确定要删除吗?\')">删</a>]';
                    }
                    if ($defaultopt && $defaultopt['btn_download_visible']) {
                        $url .= '[<a title="[右键点击下载]" href="' . $filepath . '" target="_blank">下</a>]';
                    }
                    echo '<li', $isactive, '>' . $prefix . $url . '</li>' . PHP_EOL;
                }
            }
        }
    }

    /**
     * PHP高效遍历文件夹（大量文件不会卡死,带文件名排序功能）
     * <br />外层要套上UL标签
     * @param string $path 目录路径
     * @param integer $level 目录深度
     * @param boolean $showfile 是否显示文件(否则只遍历显示目录)
     * @param array $skips 要忽略的文件路径集合
     * @param integer $deepth 扫描深度
     * @version 2018-1-11
     * @version 2018-1-13 SoChishun 新增$skips参数和$deepth
     */
    static function scandirReturn($path = './', $level = 0, $showfile = true, $skips = array(), $deepth = 0) {
        if (!file_exists($path) || ($deepth && $level > $deepth)) {
            return array();
        }
        $path = str_replace('//', '/', $path . '/');
        $file = new FilesystemIterator($path);
        $filename = '';
        $url = '';
        $prefix = ''; // 树形层级图形
        $isactive = ''; // 选中效果类
        $outarr = array();
        foreach ($file as $fileinfo) {
            $filename = iconv('GBK', 'utf-8', $fileinfo->getFilename()); // 解决中文乱码
            $filepath = $path . $filename;
            $prefix = $level > 0 ? ('|' . str_repeat('--', $level)) : '';
            if ($fileinfo->isDir()) {
                if (!in_array($filepath . '/', $skips)) {
                    $outarr[$filename] = array('path' => $filepath, 'type' => 'dir');
                    $outarr[$filename]['children'] = self::scandirReturn($filepath, $level + 1, $showfile);
                }
            } else {
                if ($showfile && !in_array($filepath, $skips)) {
                    $outarr[$filename] = array('path' => $filepath, 'type' => 'file');
                }
            }
        }
        if ($outarr) {
            ksort($outarr);
        }
        return $outarr;
    }

    /**
     * 返回指定路径下的文件信息列表
     * @param string $path 路径
     * @return array
     * @throws Exception
     * @version 1.0 <2015-5-11> SoChishun Added.
     * @version 1.1 <2015-10-8> SoChishun 新增filetype文件类别属性
     */
    static function getFileInfoList($path) {
        try {
            $dir = new DirectoryIterator($path);
        } catch (Exception $e) {
            throw $e;
        }
        $files = array();
        foreach ($dir as $file) {
            if ($file->isDot()) {
                continue;
            }
            $exten = $file->getExtension();
            $item = array(
                'name' => $file->getFileName(),
                'path' => $file->getPath(),
                'real_path' => $file->getRealPath(),
                'exten' => $exten,
                'filetype' => get_exten_catetory($exten),
                'mtime' => $file->getMTime(),
                'ctime' => $file->getCTime(),
                'size' => $file->getSize(),
                'is_dir' => $file->isDir(),
                'is_file' => $file->isFile(),
                'is_link' => $file->isLink(),
                'is_executable' => $file->isExecutable(),
                'is_readable' => $file->isReadable(),
                'is_writable' => $file->isWritable(),
            );
            $item['relative_path'] = path_ator($file->getRealPath());
            $item['filepath'] = $item['path'] . '/' . $item['name'];
            $files[] = $item;
        }
        return $files;
    }

    /**
     * 格式化文件字节大小
     * @param int $size
     * @return string
     */
    static function formatBytes($size) {
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++)
            $size /= 1024;
        return round($size, 2) . $units[$i];
    }

    /**
     * 删除非空目录里面所有文件和子目录
     * @param string $dir
     * @param boolean $delSelf 是否删除自身目录(删除还是清空)
     * @return boolean
     */
    static function rmdir($dir, $delSelf = true) {
//先删除目录下的文件：
        $dh = opendir($dir);
        while ($file = readdir($dh)) {
            if ($file != "." && $file != "..") {
                $fullpath = $dir . "/" . $file;
                if (is_dir($fullpath)) {
                    self::rmdir($fullpath, true);
                } else {
                    unlink($fullpath);
                }
            }
        }
        closedir($dh);
//删除当前文件夹：
        if (!$delSelf || rmdir($dir)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * PHP高效读取文件
     * @param string $filepath
     * @return string
     * @version 2017-11-1 return改为直接echo,否则大文件会显示空白,而且大文件整体读取不如直接用file_get_contents高效
     */
    static function tail($filepath) {
        if (file_exists($filepath)) {
            if (false !== ($fp = fopen($filepath, "r"))) {
                $buffer = 1024; //每次读取 1024 字节
                while (!feof($fp)) {//循环读取，直至读取完整个文件
                    echo htmlspecialchars(fread($fp, $buffer));
                }
                fclose($fp);
            } else {
                echo 'file can not open! [' . $filepath . ']';
            }
        } else {
            echo 'file not exists! [' . $filepath . ']';
        }
    }

    /**
     * PHP高效写入文件（支持并发）
     * @param string $filepath
     * @param string $content
     */
    static function writeFile($filepath, $content) {
        if ($fp = fopen($filepath, 'a')) {
            $startTime = microtime();
// 对文件进行加锁时，设置一个超时时间为1ms，如果这里时间内没有获得锁，就反复获得，直接获得到对文件操作权为止，当然。如果超时限制已到，就必需马上退出，让出锁让其它进程来进行操作。
            do {
                $canWrite = flock($fp, LOCK_EX);
                if (!$canWrite) {
                    usleep(round(rand(0, 100) * 1000));
                }
            } while ((!$canWrite) && ((microtime() - $startTime) < 1000));
            if ($canWrite) {
                fwrite($fp, $content);
            }
            fclose($fp);
        }
    }

    /**
     * 路径转为导航
     * @param string $path
     * @version 1.0 <2015-5-11> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 重构,提高安全性
     */
    static function showBreadcrumb($token, $path, $title = 'location: ') {
        echo '<div class="nav">', $title, '<a href="?token=' . $token . '&path=', ROOTPATH, '">/</a>';
        $asubpath = explode('/', $path);
        if ($asubpath) {
            $str = '';
            $n = count($asubpath);
            $n2 = $n - 1;
            $rootlen = strlen(ROOTPATH);
            for ($i = 0; $i < $n; $i++) {
                $sub = $asubpath[$i];
                $str .= $sub . ($i < $n2 ? '/' : '');
                if (substr($str, 0, $rootlen) != ROOTPATH) {
                    continue; // 防止显示../路径而把ROOTPATH以上的目录显示出来,造成安全问题
                }
                echo '<a href="?token=' . $token . '&path=', $str, '">', $sub, '</a>';
            }
        }
        echo '</div>';
    }

}

/**
 * 获取用户输入
 * @param string $name
 * @param mixed $defv
 * @return mixed
 * @version 1.0 <2015-8-13> SoChishun Added.
 */
function input($name, $defv = '', $filter = true) {
    if (isset($_REQUEST[$name])) {
        return $filter ? htmlspecialchars($_REQUEST[$name]) : $_REQUEST[$name];
    }
    return $defv;
}

/**
 * 截短字符串
 * @param string $str
 * @param integer $len
 * @param boolean $behind
 * @return string
 * @version 1.0 2017-8-23 SoChishun Added.
 */
function short_text($str, $len, $behind = false) {
    $strlen = strlen($str);
    if ($strlen <= $len) {
        return $str;
    }
    return $behind ? ('...' . substr($str, $strlen - $len)) : (substr($str, 0, $len) . '...');
}

/**
 * 返回信息消息
 * @param boolean $status
 * @param mixed $info
 * @return array array('status'=>true,'info'=>'message...')
 * @version 1.0 2018-1-13 SoChishun Added.
 */
function infomsg($status, $info) {
    return array('status' => $status, 'info' => $info);
}

/**
 * 倒计时跳转
 * @param boolean $status
 * @param string|array $msg
 * @param string $url
 * @param integer $time
 * @version 2017-8-24
 */
function redirect($status, $msg, $url = '', $time = 2) {
    if (!$url) {
        $url = 'history.back();';
    } else {
        $url = 'location.href="' . $url . '"';
    }
    if (is_array($msg)) {
        $mststr = implode('<br />', $msg);
    } else {
        $msgstr = str_replace('{$0}', ($status ? '成功' : '失败'), $msg);
    }
    echo '<span style="color:', ($status ? '#090' : '#F00'), '">', $msgstr, '</span>(<span id="cd">' . $time . '</span>)';
    echo '<script>function countdown(){var i=' . $time . ';var $cd=document.getElementById("cd");function fn_cd(){i--;$cd.innerText=i;if(i<1){clearInterval(itr);' . $url . '}}var itr=setInterval(fn_cd,1000)}countdown();</script>';
    exit;
}